﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Data;
using System.Xml;
using System.Xml.XPath;
using System.Threading;
using Transformer.iHunter;
using Transformer.iHunter.Database;
using Transformer.iHunter.Database.MasterDataSetTableAdapters;

namespace Transformer.iHunter
{
    /// <summary>
    /// The local client object
    /// </summary>
    public partial class LocalClient : Component, IClientCore
    {
        private IChannel activeChannel;
        private bool cancelling;
        private object activeChannelLock;
      

        #region Internal Properties

        /// <summary>
        /// Gets or sets the filter providers dictionary.
        /// </summary>
        /// <value>The filter providers dictionary.</value>
        public IDictionary<Guid, IFilterProvider> FilterProvidersDictionary
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the viewer providers dictionary.
        /// </summary>
        public IDictionary<Guid, IViewerProvider> ViewerProvidersDictionary
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets the editor providers dictionary.
        /// </summary>
        public IDictionary<Guid, IEditorProvider> EditorProvidersDictionary
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the channel providers dictionary.
        /// </summary>
        /// <value>The channel providers dictionary.</value>
        public IDictionary<Guid, IChannelProvider> ChannelProvidersDictionary
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the plugin clients dictionary.
        /// </summary>
        /// <value>The plugin clients dictionary.</value>
        internal Dictionary<Guid, IPluginClient> PluginClientsDictionary
        {
            get;
            private set;
        }

        internal MasterDataSet MasterDataSet
        {
            get
            {
                return MasterDataSet.Instance;
            }
        }
        #endregion

        #region Private Methods

        private void InitFields()
        {
            ChannelProvidersDictionary = new Dictionary<Guid, IChannelProvider>();
            FilterProvidersDictionary = new Dictionary<Guid, IFilterProvider>();
            PluginClientsDictionary = new Dictionary<Guid, IPluginClient>();
            ViewerProvidersDictionary = new Dictionary<Guid, IViewerProvider>();
            EditorProvidersDictionary = new Dictionary<Guid, IEditorProvider>();
            activeChannel = null;
            activeChannelLock = new object();
            cancelling = false;
        }
        /// <summary>
        /// Creates the channel instance.
        /// </summary>
        /// <param name="guid">The GUID.</param>
        /// <returns>The instance of the channel</returns>
        private IChannel CreateChannelInstance(Guid guid)
        {
            MasterDataSet.ChannelsRow row = this.MasterDataSet.Channels.FindByGuid(guid);

            if (row == null || row.RowState == DataRowState.Deleted)
            {

                OnErrorOccur(this, String.Format("Channel not found"));
                return null;
            }

            IChannelProvider channelProvider;

            if (!this.ChannelProvidersDictionary.TryGetValue(row.ProviderGuid, out channelProvider))
            {
                OnErrorOccur(this, String.Format("Cannot find associated plugin for channel {0}", row.Name));
                return null;
            }

            ChannelInfo channelInfo = this.GetChannelInfoFromRow(row);

            IChannel channel = channelProvider.CreateChannelInstance(channelInfo);

            if (channel == null)
            {
                return null;
            }
            else
            {
                ChannelWrapper wrapper = new ChannelWrapper(channel);
                wrapper.ErrorOccur += delegate(object sender, PluginErrorEventArgs args)
                {
                    this.OnErrorOccur(this, args.ErrorMessage);
                };
                return wrapper;
            }

        }


        private void SynchronizeChannel(MasterDataSet.ChannelsRow channelRow)
        {


            Monitor.Enter(activeChannelLock);

            if (cancelling)
            {
                Monitor.Exit(activeChannelLock);
                return;
            }
            cancelling = false;
            Monitor.Exit(activeChannelLock);

            IFolder folder = null;

            var row = this.MasterDataSet.Folders.FindByGuid(channelRow.FolderGuid);

            if (row == null || row.RowState == DataRowState.Deleted || row.ParentGuid == DeletedItems.Guid)
                folder = this.MasterDataSet.Folders.Inbox;
            else
                folder = row;

            IChannel channel = CreateChannelInstance(channelRow.Guid);

            if (channel == null)
                return;

            Monitor.Enter(activeChannelLock);
            activeChannel = channel;
            Monitor.Exit(activeChannelLock);

            string attachmentDirecotry = GetDefaultAttachmentDirectory(folder);

            DateTime time = channelRow.UpdateTime;

            bool downloadAttachments = false;
            bool checkExists = false;

            XmlDocument document = new XmlDocument();
            XPathNavigator nav = document.CreateNavigator();
            try
            {
                document.LoadXml(this.GlobalSettings);


                int days = nav.SelectSingleNode("/GlobalSettings/TimeSpan").ValueAsInt;
                TimeSpan timeSpan = new TimeSpan(days, 0, 0, 0);

                if (time <= miniTime)
                    time = DateTime.Now - timeSpan;

                checkExists = nav.SelectSingleNode("/GlobalSettings/CheckExists").ValueAsBoolean;

                downloadAttachments = nav.SelectSingleNode("/GlobalSettings/DownloadAttachments").ValueAsBoolean;

            }
            catch
            {
            }


            try
            {
                Directory.CreateDirectory(attachmentDirecotry);
            }
            catch
            {
                OnErrorOccur(this, string.Format("Cannot create directory {0}", attachmentDirecotry));
                return;
            }

            try
            {
                IEnumerable<IMessage> downloadedMessagesEnumerable = channel.DownloadMessages(time, DateTime.Now, attachmentDirecotry);
                EnumerateDownloadedMessages(channelRow.Guid, folder, channel, downloadAttachments, downloadedMessagesEnumerable, true);

                ChannelInfo info = GetChannelInfo(channelRow.Guid);
                info.UpdateTime = DateTime.Now;
                info.FolderGuid = folder.Guid;
                SetChannelInfo(info);
            }
            catch (System.Net.Sockets.SocketException)
            {
            }
            catch (System.Net.WebException)
            {
            }
            catch (System.InvalidOperationException)
            {
            }
            catch
            {
                //TODO: Disable the buggy plugin.
            }
        }

        private void EnumerateDownloadedMessages(Guid channelGuid, IFolder folder, IChannel channel, bool downloadAttachments, IEnumerable<IMessage> downloadedMessagesEnumerable, bool createCategoryFolder)
        {
            foreach (IMessage msg in downloadedMessagesEnumerable)
            {
                Monitor.Enter(activeChannelLock);
                if (cancelling)
                {
                    Monitor.Exit(activeChannelLock);
                    channel.CancelBlockingCall();
                    break;
                }
                Monitor.Exit(activeChannelLock);


                IFolder target = null;

                if (createCategoryFolder && msg.Category.Trim().Length > 0)
                {
                    target = FindFolderByName(msg.Category, folder.Guid);

                    if (target == null)
                    {
                        this.NewFolder(new LocalFolder()
                        {
                            Name = msg.Category,
                            Settings = null
                        },
                           folder.Guid
                           );
                        target = folder;
                        target = FindFolderByName(msg.Category, folder.Guid);

                    }
                }
                else
                {
                    target = folder;
                }
                Debug.Assert(target != null);


                Message downloadedMessage = null;
                Guid newGuid = Guid.Empty;



                if (msg.HashCode == 0)
                {
                    downloadedMessage = new Message(Guid.Empty, msg);
                    downloadedMessage.ChannelGuid = channelGuid;
                    downloadedMessage.Read = false;
                    newGuid = this.NewMessage(downloadedMessage, target.Guid);
                }
                else
                {
                    DataRow[] existingRows = this.MasterDataSet.MessageHeaders.Select(string.Format("HashCode = {0} AND ChannelGuid = '{1}'", msg.HashCode, channelGuid));

                    IMessage existingMessage = ((existingRows.Length == 0) ? null : existingRows[0]) as IMessage;

                    if (existingMessage == null)
                    {
                        downloadedMessage = new Message(Guid.Empty, msg);
                        downloadedMessage.Read = false;
                        downloadedMessage.ChannelGuid = channelGuid;
                        newGuid = this.NewMessage(downloadedMessage, target.Guid);

                    }
                    else
                    {
                        newGuid = existingMessage.Guid;
                        downloadedMessage = new Message(existingMessage.Guid, msg);
                        downloadedMessage.ChannelGuid = channelGuid;
                        if (!existingMessage.Read)
                            downloadedMessage.Read = false;
                        this.StoreMessage(downloadedMessage);
                    }
                }


                if (downloadAttachments)
                {
                    IMessage storedMessage = FindMessageHeaderByGuid(newGuid);
                    foreach (IAttachment attachment in storedMessage.Attachments)
                    {
                        DownloadAttachment(attachment.Guid);
                    }
                }

                //Notify message change
                OnMessageChanged(downloadedMessage);

            }
        }

        private static string GetDefaultAttachmentDirectory(IFolder folder)
        {
            Stack<string> pathNames = new Stack<string>();

            IFolder cursor = folder;


            while (!cursor.IsRoot)
            {
                pathNames.Push(cursor.Name);
                cursor = cursor.Parent;
            }

            string attachmentPath = (string)(AppDomain.CurrentDomain.GetData("DataDirectory"));

            while (pathNames.Count > 0)
            {
                attachmentPath = Path.Combine(attachmentPath, pathNames.Pop());
            }
            return attachmentPath;
        }

        public void DownloadAttachment(Guid attachmentGuid)
        {
            MasterDataSet.AttachmentsRow attachment = MasterDataSet.Attachments.FindByGuid(attachmentGuid);
            if (attachment == null)
                return;

            IMessage header = FindMessageHeaderByGuid(attachment.MessageGuid);
            if (header == null)
                return;

            DownloadAttachment(header, attachment);
        }

        private void DownloadAttachment(IMessage message, IAttachment attachment)
        {
            if (attachment.LocalPath.Trim().Length == 0)
            {
                attachment.LocalPath = this.GetAttachmentLocalPath(message, attachment);
                try
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(attachment.LocalPath));
                }
                catch
                {
                    OnErrorOccur(this, "Cannot create directory");
                    attachment.LocalPath = "";
                    return;
                }
            }

            IChannel channel = CreateChannelInstance(message.ChannelGuid);
            if (channel == null)
            {
                attachment.LocalPath = "";
                return;
            }
            if (!channel.DownloadAttachment(attachment))
            {
                attachment.LocalPath = "";
                OnErrorOccur(channel, "Failed to download attachment.");
            }
            
        }
        private ChannelInfo GetChannelInfoFromRow(MasterDataSet.ChannelsRow row)
        {
            ChannelInfo info = new ChannelInfo();
            info.Guid = row.Guid;
            info.ProviderGuid = row.ProviderGuid;
            info.FolderGuid = row.FolderGuid;
            info.Settings = row.Settings;
            info.Name = row.Name;
            info.UpdateTime = row.UpdateTime;
            switch (row.RefreshExists)
            {
                case 1:
                    info.RefreshExists = true;
                    break;
                case 2:

                    info.RefreshExists = false;
                    break;
                case 0:
                default:
                    info.RefreshExists = null;
                    break;
            }
            return info;
        }

        public IAttachment FindAttachmentByGuid(Guid guid)
        {
            return MasterDataSet.Attachments.FindByGuid(guid);
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="LocalClient"/> class.
        /// </summary>
        public LocalClient()
        {

            InitializeComponent();
            InitFields();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LocalClient"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        public LocalClient(IContainer container)
        {
            container.Add(this);

            InitializeComponent();

            InitFields();
        }

        #endregion

        #region Public Properties


        /// <summary>
        /// Gets the database manager.
        /// </summary>
        /// <value>The database manager.</value>
        public IDatabaseManager DatabaseManager
        {
            get
            {
                return this.MasterDataSet;
            }
        }

        /// <summary>
        /// Gets the plugin manager.
        /// </summary>
        /// <value>The plugin manager.</value>
        public PluginManager PluginManager
        {
            get
            {
                return this.pluginManager;
            }
        }


        public IFolder Root
        {
            get
            {
                return this.MasterDataSet.Folders.Root;
            }
        }

        public IFolder Inbox
        {
            get
            {
                return this.MasterDataSet.Folders.Inbox;
            }
        }

        public IFolder Outbox
        {
            get
            {
                return this.MasterDataSet.Folders.Outbox;
            }
        }

        public IFolder Drafts
        {
            get
            {
                return this.MasterDataSet.Folders.Drafts;
            }
        }


        public IFolder SentItems
        {
            get
            {
                return this.MasterDataSet.Folders.SentItems;
            }
        }

        public IFolder DeletedItems
        {
            get
            {
                return this.MasterDataSet.Folders.DeletedItems;
            }
        }


        #endregion






        #region Public Methods

      
        /// <summary>
        /// Gets the information of all channels
        /// </summary>
        /// <returns>The information of channels</returns>
        public ChannelInfo[] GetChannelInfos()
        {
            List<ChannelInfo> channelInfos = new List<ChannelInfo>();
            int index = 0;
            foreach (MasterDataSet.ChannelsRow row in this.MasterDataSet.Channels)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                ChannelInfo info = this.GetChannelInfoFromRow(row);

                channelInfos.Add(info);
            }
            return channelInfos.ToArray();
        }

        /// <summary>
        /// Get channel infomation
        /// </summary>
        /// <param name="guid">The Guid</param>
        public ChannelInfo GetChannelInfo(Guid guid)
        {
            MasterDataSet.ChannelsRow row = this.MasterDataSet.Channels.FindByGuid(guid);

            ChannelInfo info = new ChannelInfo(); ;
            if (row == null || row.RowState == DataRowState.Deleted)
            {
                info.Guid = Guid.Empty;
            }
            else
            {
                info.Guid = row.Guid;
                info.ProviderGuid = row.ProviderGuid;
                info.FolderGuid = row.FolderGuid;
                info.Settings = row.Settings;
                info.Name = row.Name;
                switch (row.RefreshExists)
                {
                    case 1:
                        info.RefreshExists = true;
                        break;
                    case 2:
                        info.RefreshExists = false;
                        break;
                    case 0:
                    default:
                        info.RefreshExists = null;
                        break;
                } 
                info.UpdateTime = row.UpdateTime;
            }
            return info;
        }


        /// <summary>
        /// Adds the channel.
        /// </summary>
        /// <param name="channelInfo">The channel info.</param>
        public void AddChannle(ChannelInfo channelInfo)
        {
            var folderRow = this.MasterDataSet.Folders.FindByGuid(channelInfo.FolderGuid);
            if (folderRow == null || folderRow.RowState == DataRowState.Deleted)
                channelInfo.FolderGuid = this.MasterDataSet.Folders.Inbox.Guid;

            MasterDataSet.ChannelsRow row = this.MasterDataSet.Channels.NewChannelsRow();
            row.Guid = Guid.NewGuid();
            row.Name = channelInfo.Name;
            row.ProviderGuid = channelInfo.ProviderGuid;
            row.FolderGuid = channelInfo.FolderGuid;
            if (channelInfo.RefreshExists.HasValue)
                row.RefreshExists = ((channelInfo.RefreshExists.Value) ? 1 : 2);
            else
                row.RefreshExists = 0;

            row.Settings = channelInfo.Settings;
            row.UpdateTime = (channelInfo.UpdateTime > miniTime) ? channelInfo.UpdateTime : miniTime;
            this.MasterDataSet.Channels.AddChannelsRow(row);
        }



        /// <summary>
        /// Remove a channel that is stored in database
        /// </summary>
        /// <param name="guid">Guid specifying a channel.</param>
        public void RemoveChannel(Guid guid)
        {
            MasterDataSet.ChannelsRow row = this.MasterDataSet.Channels.FindByGuid(guid);
            if (row == null || row.RowState == DataRowState.Deleted)
            {
                throw new ArgumentException("Invalid Guid");
            }

            row.Delete();
        }


        /// <summary>
        /// Set channel info
        /// </summary>
        /// <param name="channelInfo">Channel info.</param>
        public void SetChannelInfo(ChannelInfo channelInfo)
        {
            MasterDataSet.ChannelsRow row = this.MasterDataSet.Channels.FindByGuid(channelInfo.Guid);
            if (row == null || row.RowState == DataRowState.Deleted)
            {
                throw new ArgumentException("Invalid Guid");
            }

            row.Name = channelInfo.Name;
            row.ProviderGuid = channelInfo.ProviderGuid;
            row.FolderGuid = channelInfo.FolderGuid;

            if (channelInfo.RefreshExists.HasValue)
                row.RefreshExists = ((channelInfo.RefreshExists.Value) ? 1 : 2);
            else
                row.RefreshExists = 0;

            row.Settings = channelInfo.Settings;
            row.UpdateTime = (channelInfo.UpdateTime > miniTime) ? channelInfo.UpdateTime : miniTime;
        }

        private static DateTime miniTime = new DateTime(1800, 1, 1);

        /// <summary>
        /// Store plugin settings
        /// </summary>
        /// <param name="pluginGuid">The Guid</param>
        /// <param name="settings">The settings</param>
        /// <returns>Ture if succeed, otherwise false.</returns>
        public bool StorePluginSettings(Guid pluginGuid, string settings)
        {
            MasterDataSet.PluginsRow row = this.MasterDataSet.Plugins.FindByGuid(pluginGuid);
            if (row == null || row.RowState == DataRowState.Deleted)
                return false;

            row.Settings = settings;
            return true;
            //if (TableAdapterManager.Instance.PluginsTableAdapter.Update(row) == 0)
            //{
            //    row.RejectChanges();
            //    return false;
            //}
            //else
            //{
            //    return true;
            //}
        }

        /// <summary>
        /// Synchronizes a channel
        /// </summary>
        /// <param name="channelGuid">The guid of the channel</param>
        public void SynchronizeChannel(Guid channelGuid)
        {
            MasterDataSet.ChannelsRow channelRow = this.MasterDataSet.Channels.FindByGuid(channelGuid);
            if (channelRow == null || channelRow.RowState == DataRowState.Deleted)
            {
                OnErrorOccur(this, String.Format("Channel not found"));
                return;
            }

            SynchronizeChannel(channelRow);
        }


        public void UpdateDataset()
        {
            TableAdapterManager.Instance.UpdateAll(this.MasterDataSet);
            foreach (var pluginClient in this.PluginClientsDictionary.Values)
            {
                pluginClient.StorePluginDatabase();
            }
        }

        #endregion

        #region IClientCore Members

        /// <summary>
        /// The root folder.
        /// </summary>
        public IFolder RootFolder
        {
            get
            {
                return this.MasterDataSet.Folders.Root;
            }
        }


        /// <summary>
        /// Finds the folder by GUID.
        /// </summary>
        /// <param name="guid">The GUID.</param>
        /// <returns>
        /// The folder object if found, <c>null</c> is the folder of the specified GUID does not exist
        /// </returns>
        public IFolder FindFolderByGuid(Guid guid)
        {
            return this.MasterDataSet.Folders.FindByGuid(guid);

        }
        /// <summary>
        /// Finds the message by GUID.
        /// </summary>
        /// <param name="guid">The GUID.</param>
        /// <returns>
        /// The message object if found, <c>null</c> if the message of the specified GUID not found
        /// </returns>
        public IMessage FindMessageByGuid(Guid guid)
        {
            var header = this.MasterDataSet.MessageHeaders.FindByGuid(guid);
            if (header == null || header.RowState == DataRowState.Deleted)
                return null;


            var table = TableAdapterManager.Instance.MessagesTableAdapter.GetDataByGuid(guid);
            if (table.Count == 0)
            {
                return null;
            }

            return table[0];
        }

        public IMessage FindMessageHeaderByGuid(Guid guid)
        {
            return MasterDataSet.MessageHeaders.FindByGuid(guid);
        }

        /// <summary>
        /// Store a folder, update all changes.
        /// </summary>
        /// <param name="folder">The folder.</param>
        public bool StoreFolder(IFolder folder)
        {
            var dataRows = this.MasterDataSet.Folders.Select(
            string.Format("NAME = '{0}' AND PARENTGUID = '{1}' AND GUID <> '{2}'",
                folder.Name, folder.Parent.Guid, folder.Guid));
            if (this.NumOfInUseRows(dataRows) != 0)
                return false;

            var row = this.MasterDataSet.Folders.FindByGuid(folder.Guid);
            if (row == null || row.RowState == DataRowState.Deleted)
                return false;

            row.Name = folder.Name;
            row.Settings = folder.Settings;
            this.OnFolderChanged(folder);
            return true;
        }

        private int NumOfInUseRows(DataRow[] rows)
        {
            int k = 0;
            foreach (DataRow row in rows)
            {
                if (row.RowState != DataRowState.Deleted)
                    k++;
            }
            return k;
        }

        private void SynchronizeFolder(MasterDataSet.FoldersRow folderRow, bool recursive)
        {
            Monitor.Enter(activeChannelLock);

            if (cancelling)
            {
                Monitor.Exit(activeChannelLock);
                return;
            }
            cancelling = false;
            Monitor.Exit(activeChannelLock);
            
            if (recursive)
            {
                foreach (MasterDataSet.FoldersRow subfolder in folderRow.SubFolders)
                {
                    if (subfolder.RowState != DataRowState.Deleted)
                        SynchronizeFolder(subfolder, recursive);
                }
            }

            if (this.MasterDataSet.Folders.Inbox.IsAncestorOf(folderRow))
            {
                //Step 1. Find all channels that will put messages in this folder
                //(Do not include channels put things in its parent folder)

                //Step 2. Synchronize the channels
                //use SynchronizeChannel()


                for (int i = 0; i < this.MasterDataSet.Channels.Count; i++)
                {
                    
                    MasterDataSet.ChannelsRow row = MasterDataSet.Channels[i];
               
                    if (row.RowState != DataRowState.Deleted && row.FolderGuid == folderRow.Guid)
                    {
                        SynchronizeChannel(row);

                    }
                } 
                
                bool globalCheckExists = false;
                XmlDocument document = new XmlDocument();
                XPathNavigator nav = document.CreateNavigator();
                try
                {
                    globalCheckExists = nav.SelectSingleNode("/GlobalSettings/CheckExists").ValueAsBoolean;
                }
                catch
                {
                } 
                
                foreach (IMessage msg in this.GetMessageHeaders(folderRow.Guid))
                {
                    Monitor.Enter(activeChannelLock);

                    if (cancelling)
                    {
                        Monitor.Exit(activeChannelLock);
                        return;
                    }
                    Monitor.Exit(activeChannelLock);

                    ChannelInfo info = GetChannelInfo(msg.ChannelGuid);
                    if (!this.ChannelProvidersDictionary.ContainsKey(info.ProviderGuid))
                    {
                        continue;
                    }

                    if (
                        (info.RefreshExists.HasValue && info.RefreshExists.Value) ||
                        (!info.RefreshExists.HasValue && globalCheckExists)
                        )
                    {

                        SynchronizeMessage(msg.Guid);
                    }
                }
                //Do not check updates for existing items in the folder

            }
            else if (this.MasterDataSet.Folders.Outbox.IsAncestorOf(folderRow))
            {
                //Step 1. Get each messages in the folder

                //Step 2. Determine whether it is new item or reply
                //NOTE: If you cannot find the original message of a reply, take it as a new message
                foreach (MasterDataSet.MessageHeadersRow row in folderRow.MessageHeaders)
                {
                    //Step 3. Send or reply each item
                    //NOTE: Call SynchronizeMessage()
                    SynchronizeMessage(row.Guid);
                }
            }
            else
            {
                //Do nothing
            }



            //ICollection<IMessage> messages = folderRow.MessagesHeaders;
            //foreach (MasterDataSet.ChannelsRow crow in folderRow.GetChannelsRows())
            //{
            //    IChannel channel = CreateChannelInstance(crow.Guid);
         
            //    //FIXME: Attachments path
            //    foreach (IMessage msg in channel.DownloadMessages(crow.UpdateTime, DateTime.Now,
            //        System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments)))
            //    {
            //        messages.Add(msg);
            //    }
            //}
        }

        /// <summary>
        /// Creates the database.
        /// </summary>
        public void CreateMasterDatabase()
        {
            this.MasterDataSet.DeleteDataBase();
            this.MasterDataSet.CreateDataBase();
        }

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        public void Initailize()
        {
            InitFields();
            if (!this.MasterDataSet.IsDatabaseExist())
                throw new System.IO.FileNotFoundException();

            this.MasterDataSet.Initialize();
            pluginManager.Initialize(this);
            pluginManager.PluginInternalException += new PluginErrorEventHandler(pluginManager_PluginInternalException);

            this.Disposed += new EventHandler(LocalClient_Disposed);

            //System.Timers.Timer updateTimer = new System.Timers.Timer();
            //updateTimer.Elapsed += new System.Timers.ElapsedEventHandler(updateTimer_Elapsed);
            //updateTimer.Interval = 5000;
            //updateTimer.Enabled = false;
        }

        void LocalClient_Disposed(object sender, EventArgs e)
        {
            SaveDatabase();
        }

        private void SaveDatabase()
        {
            TableAdapterManager.Instance.AttachmentsTableAdapter.Update(MasterDataSet);
            TableAdapterManager.Instance.UpdateAll(MasterDataSet);

            foreach (var pluginClient in this.PluginClientsDictionary.Values)
            {
                pluginClient.StorePluginDatabase();
            }
        }

        private void updateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            //For update folders


            //For update messages
            TableAdapterManager.Instance.MessageHeadersTableAdapter.Update(this.MasterDataSet.MessageHeaders);
        }

        void pluginManager_PluginInternalException(object sender, PluginErrorEventArgs e)
        {
            IPlugin plugin = e.Target as IPlugin;
            try
            {
                pluginManager.DisablePlugin(plugin.Guid);
            }
            catch
            {
            }

            OnErrorOccur(plugin, "Plugin Error" + e.ErrorMessage);
        }



        /// <summary>
        /// Synchronizes all folders
        /// </summary>
        public void SynchronizeAll()
        {
            foreach (ChannelInfo channel in this.GetChannelInfos())
            {
                Monitor.Enter(activeChannelLock);

                if (cancelling)
                {
                    Monitor.Exit(activeChannelLock);
                    return;
                }
                Monitor.Exit(activeChannelLock);

                SynchronizeChannel(channel.Guid);
            }

            bool globalCheckExists = false;
            XmlDocument document = new XmlDocument();
            XPathNavigator nav = document.CreateNavigator();
            try
            {
                globalCheckExists = nav.SelectSingleNode("/GlobalSettings/CheckExists").ValueAsBoolean;
            }
            catch
            {
            }

            foreach (MasterDataSet.MessageHeadersRow msg in this.MasterDataSet.MessageHeaders)
            {
                Monitor.Enter(activeChannelLock);

                if (cancelling)
                {
                    Monitor.Exit(activeChannelLock);
                    return;
                }
                Monitor.Exit(activeChannelLock);

                if (msg.RowState == DataRowState.Deleted)
                    continue;

                ChannelInfo info = GetChannelInfo(msg.ChannelGuid);
                if (!this.ChannelProvidersDictionary.ContainsKey(info.ProviderGuid))
                {
                    continue;
                }

                if (
                    (info.RefreshExists.HasValue && info.RefreshExists.Value) ||
                    (!info.RefreshExists.HasValue && globalCheckExists)
                    )
                {

                    SynchronizeMessage(msg.Guid);
                }
            }

            SynchronizeFolder(this.Outbox.Guid);
        }


        /// <summary>
        /// Synchronizes the folder.
        /// </summary>
        /// <param name="guid">The GUID of the folder.</param>
        public void SynchronizeFolder(Guid guid)
        {
            var row = this.MasterDataSet.Folders.FindByGuid(guid);

            SynchronizeFolder(row, true);
        }

        private void SynchronizeMessage(MasterDataSet.MessagesRow msg)
        {

            Monitor.Enter(activeChannelLock);

            if (cancelling)
            {
                Monitor.Exit(activeChannelLock);
                return;
            }
            cancelling = false;
            Monitor.Exit(activeChannelLock);
            
            IChannel channel = CreateChannelInstance(msg.ChannelGuid);

            if (channel == null)
                return;

            Message newMsg = new Message(msg.Guid, msg);

            // Deal with special folders
            //For example, synchronize a message in [outbox] means send it, if successful, move it to sent items;
            if (this.MasterDataSet.Folders.Outbox.IsAncestorOf(newMsg.Folder))
            {
                TableAdapterManager.Instance.MessagesTableAdapter.Update(msg);

                // Send the messages in Outbox folder and move it to "[Sent Items]"
                IMessage origMessage = this.FindMessageByGuid(newMsg.ReplyGuid);
                bool success = false;
                if (origMessage == null)
                {
                    success = channel.SendMessage(newMsg);
                }
                else
                {
                    success = channel.ReplyMessage(origMessage,newMsg);

                }

                if (success)
                {
                    newMsg.Read = true;
                    StoreMessage(newMsg);
                    MoveMessage(msg.Guid, this.MasterDataSet.Folders.SentItems.Guid);

                }
                else
                {
                    msg.RejectChanges();
                }
            }
            else if (this.MasterDataSet.Folders.Inbox.IsAncestorOf(newMsg.Folder))
            {
                EnumerateDownloadedMessages(msg.ChannelGuid,msg.Folder,channel,true, channel.UpdateMessage(newMsg),false);
                StoreMessage(newMsg);
            }
            else
            {
                //Do nothing
            }
        }

        /// <summary>
        /// Synchronizes the message.
        /// </summary>
        /// <param name="guid">The GUID.</param>
        public void SynchronizeMessage(Guid guid)
        {
            MasterDataSet.MessagesDataTable msgTable =
                TableAdapterManager.Instance.MessagesTableAdapter.GetDataByGuid(guid);

            if (msgTable.Count == 0)
            {
                OnErrorOccur(this,  "Message not found!");
            }

            SynchronizeMessage(msgTable[0]);
        }

        /// <summary>
        /// Gets or sets the global settings.
        /// </summary>
        /// <value>The global settings.</value>
        public string GlobalSettings
        {
            get
            {
                return this.MasterDataSet.GlobalSettings[0].Settings;
            }
            set
            {
                this.MasterDataSet.GlobalSettings[0].Settings = value;
            }
        }
        #endregion

        #region IClientCore 成员

        protected virtual void OnErrorOccur(object target, string message)
        {
            if (ErrorOccur != null)
                ErrorOccur(this, new PluginErrorEventArgs(target, message));
        }
        public event PluginErrorEventHandler ErrorOccur;


        /// <summary>
        /// Cancels the blocking call.
        /// </summary>
        public void CancelBlockingCall()
        {
            
            Monitor.Enter(activeChannelLock);

            try
            {
                cancelling = true;
                
                if (activeChannel == null)
                    return;

                IChannel cancellingChannel = activeChannel;
                cancellingChannel.CancelBlockingCall();
                
            }
            catch
            {
                throw;
            }
            finally
            {
                activeChannel = null;
                cancelling = false;
                Monitor.Exit(activeChannelLock);
            }
        }

      

        protected virtual void OnMessageChanged(IMessage msg)
        {
            if (MessageChanged != null)
            {
                MessageChanged(this, new MessageEventArgs(msg));
            }
        }

        protected virtual void OnFolderChanged(IFolder folder)
        {
            if (FolderChanged != null)
                FolderChanged(this, new FolderEventArgs(folder));
        }

        public event MessageEventHandler MessageChanged;
        public event FolderEventHandler FolderChanged;


        #endregion



        #region IClientCore 成员


        public IFolder[] GetSubFolders(Guid parentGuid)
        {
            MasterDataSet.FoldersRow folderRow = this.MasterDataSet.Folders.FindByGuid(parentGuid);

            if (folderRow == null || folderRow.RowState == DataRowState.Deleted)
                return new IFolder[0];

            List<IFolder> list = new List<IFolder>(folderRow.SubFolders);
            return list.ToArray();
        }

        public Guid NewFolder(IFolder folder, Guid parentGuid)
        {
            var dataRows = this.MasterDataSet.Folders.Select(
                    string.Format("NAME = '{0}' AND PARENTGUID = '{1}'",
                    folder.Name, parentGuid));
            if (this.NumOfInUseRows(dataRows) != 0)
                return Guid.Empty;

            MasterDataSet.FoldersRow row = this.MasterDataSet.Folders.NewFoldersRow();
            row.Guid = Guid.NewGuid();
            row.ParentGuid = parentGuid;
            row.Name = folder.Name;
            row.Settings = folder.Settings;

            this.MasterDataSet.Folders.AddFoldersRow(row);


            this.OnFolderChanged(row);
            return row.Guid;
        }

        public void DeleteFolder(Guid guid)
        {
            MasterDataSet.FoldersRow row = this.MasterDataSet.Folders.FindByGuid(guid);
            if (row == null || row.RowState == DataRowState.Deleted)
                return;

            row.Delete();
            
            this.OnFolderChanged(row);
        }

        public void MoveFolder(Guid folderGuid, Guid newParentGuid)
        {
            MasterDataSet.FoldersRow row = this.MasterDataSet.Folders.FindByGuid(folderGuid);
            if (row == null || row.RowState == DataRowState.Deleted)
                return;
            if (newParentGuid == DeletedItems.Guid)
            {
                MasterDataSet.ChannelsRow[] chRows = row.GetChannelsRows();
                for (int i = 0; i < chRows.Length; i++)
                {
                    if (chRows[i].RowState == DataRowState.Deleted)
                        continue;
                    chRows[i].FolderGuid = this.Inbox.Guid;
                }
            }
            row.ParentGuid = newParentGuid;
            this.OnFolderChanged(row);
        }

        public Guid CopyFolder(Guid folderGuid, Guid newParentGuid)
        {
            Guid guid = this.CopyFolderRecursive(folderGuid, newParentGuid);
            if (guid == Guid.Empty)
            {
                this.MasterDataSet.Folders.RejectChanges();
                this.MasterDataSet.Messages.RejectChanges();
                return Guid.Empty;
            }
            else
            {
                return guid;
            }
        }

        private Guid CopyFolderRecursive(Guid folderGuid, Guid newParentGuid)
        {

            MasterDataSet.FoldersRow row = this.MasterDataSet.Folders.FindByGuid(folderGuid);
            if (row == null || row.RowState == DataRowState.Deleted)
                return Guid.Empty;

            MasterDataSet.FoldersRow newFolderRow = this.MasterDataSet.Folders.NewFoldersRow();
            newFolderRow.Guid = Guid.NewGuid();
            newFolderRow.Name = row.Name;
            newFolderRow.ParentGuid = newParentGuid;
            newFolderRow.Settings = row.Settings;

            foreach (IFolder folder in row.SubFolders)
            {
                if (CopyFolder(folder.Guid, newFolderRow.Guid) == Guid.Empty)
                    return Guid.Empty;
            }
            
            foreach (var message in row.MessageHeaders)
            {
                if (this.CopyMessage(message.Guid, newFolderRow.Guid) == Guid.Empty)
                    return Guid.Empty;
            }

            return newFolderRow.Guid;
        }



        private Guid GetOriginalFolderGuid(Guid folderGuid)
        {
            MasterDataSet.FoldersRow folder = this.MasterDataSet.Folders.FindByGuid(folderGuid);
            if (folder == null || folder.RowState == DataRowState.Deleted)
                return Guid.Empty;

            IFolder originalFolder = folder;
            while (originalFolder.Parent != this.Root)
            {
                originalFolder = originalFolder.Parent;
            }

            return originalFolder.Guid;
        }

        public Guid NewMessage(IMessage message, Guid folderGuid)
        {
            //If it is a valid folder
            var folderRow = MasterDataSet.Folders.FindByGuid(folderGuid);

            if (folderRow == null || folderRow.RowState == DataRowState.Deleted)
                return Guid.Empty;


            //new Guid
            Guid guid = Guid.NewGuid();

            DateTime updateTime = (message.UpdateTime > miniTime) ? message.UpdateTime : miniTime;
            DateTime publishTime = (message.PublishTime > miniTime) ? message.PublishTime : miniTime;

            //add a row to messageheader(only in dataset)
            var header = this.MasterDataSet.MessageHeaders.NewMessageHeadersRow();
            header.Guid = guid;
            header.FolderGuid = folderGuid;
            header.Read = message.Read;
            header.Title = message.Title;
            header.PublishTime = publishTime;
            header.ChannelGuid = message.ChannelGuid;

            header.UpdateTime = updateTime;
            header.Author = message.Author;
            header.Category = message.Category;
            header.ReplyGuid = message.ReplyGuid;
            header.Receiver = message.Receiver;
            header.HashCode = message.HashCode;

            Guid originalFolderGuid = this.GetOriginalFolderGuid(folderGuid);
            header.OriginalFolderGuid = originalFolderGuid;

            this.MasterDataSet.MessageHeaders.AddMessageHeadersRow(header);

            header.AcceptChanges();

            TableAdapterManager.Instance.UpdateAll(this.MasterDataSet);
            //add a row to message
            int res = TableAdapterManager.Instance.MessagesTableAdapter.Insert(guid, folderGuid,
              message.ChannelGuid, message.Read, message.Title, publishTime, updateTime,
              message.Author, message.Category, message.Content, null,
              message.ReplyGuid, message.Receiver, message.ViewerGuid, message.EditorGuid, message.HashCode, originalFolderGuid);

            if (res == 0)
            {
                header.RejectChanges();
                return Guid.Empty;
            }
            
            //update customfields
            var table = TableAdapterManager.Instance.MessagesTableAdapter.GetDataByGuid(guid);

            if (table.Count == 0)
            {
                header.RejectChanges();
                return Guid.Empty;
            }

            var msg = table[0];

            foreach (var item in message.CustomFields)
            {
                (msg as IMessage).CustomFields.Add(item);
            }


            foreach (IAttachment attachment in message.Attachments)
            {
                MasterDataSet.Attachments.AddAttachmentsRow(
                    Guid.NewGuid(),
                    header,
                    attachment.FileName,
                    attachment.Size,
                    attachment.RemoteUrl,
                    attachment.LocalPath,
                    attachment.Description
                    );
            }

            //update message row
            TableAdapterManager.Instance.MessagesTableAdapter.Update(msg);
            header.AcceptChanges();
            this.OnMessageChanged(msg);
            return guid;
        }

        public IMessage[] GetMessageHeaders(Guid folderGuid)
        {
            MasterDataSet.FoldersRow folderRow = this.MasterDataSet.Folders.FindByGuid(folderGuid);

            if (folderRow == null || folderRow.RowState == DataRowState.Deleted)
                return new IMessage[0];

            List<IMessage> list = new List<IMessage>(folderRow.MessageHeaders);
            return list.ToArray() ;
        }

        public void MoveMessage(Guid guid, Guid folderGuid)
        {
            //update message header
            var header = this.MasterDataSet.MessageHeaders.FindByGuid(guid);

            if (header == null || header.RowState == DataRowState.Deleted)
                return;

            header.FolderGuid = folderGuid;

            
            //update message
            var table = TableAdapterManager.Instance.MessagesTableAdapter.GetDataByGuid(guid);

            if (table.Count == 0)
            {
                header.RejectChanges();
                return;
            }

            var msg = table[0];
            msg.FolderGuid = folderGuid;

            TableAdapterManager.Instance.UpdateAll(this.MasterDataSet);
            TableAdapterManager.Instance.MessagesTableAdapter.Update(msg);
            header.AcceptChanges();
            this.OnMessageChanged(msg);
        }

        public Guid CopyMessage(Guid guid, Guid folderGuid)
        {
            MasterDataSet.MessageHeadersRow header = this.MasterDataSet.MessageHeaders.FindByGuid(guid);
            if (header == null || header.RowState == DataRowState.Deleted)
                return Guid.Empty;

            var table = TableAdapterManager.Instance.MessagesTableAdapter.GetDataByGuid(guid);
            if (table.Count == 0)
            {
                return Guid.Empty;
            }

            Guid newGuid = this.NewMessage(table[0], folderGuid);

            return newGuid;
        }

        public void StoreMessage(IMessage message)
        {
            //Update message header
            MasterDataSet.MessageHeadersRow header = this.MasterDataSet.MessageHeaders.FindByGuid(message.Guid);
            if (header == null || header.RowState == DataRowState.Deleted)
                return ;

            DateTime updateTime = (message.UpdateTime > miniTime) ? message.UpdateTime : miniTime;
            DateTime publishTime = (message.PublishTime > miniTime) ? message.PublishTime : miniTime;

            header.Read = message.Read;
            header.Title = message.Title;
            header.PublishTime = publishTime;

            header.UpdateTime = updateTime;
            header.Author = message.Author;
            header.Category = message.Category;
            header.ReplyGuid = message.ReplyGuid;
            header.Receiver = message.Receiver;
            header.HashCode = message.HashCode;


            //Update message
            var table = TableAdapterManager.Instance.MessagesTableAdapter.GetDataByGuid(message.Guid);
            if (table.Count == 0)
            {
                header.RejectChanges();
                return;
            }

            var messageRow = table[0];
            messageRow.Read = message.Read;
            messageRow.Title = message.Title;
            messageRow.PublishTime = publishTime;

            messageRow.UpdateTime = updateTime;
            messageRow.Author = message.Author;
            messageRow.Category = message.Category;
            messageRow.ReplyGuid = message.ReplyGuid;
            messageRow.Receiver = message.Receiver;
            messageRow.HashCode = message.HashCode;

            messageRow.ChannelGuid = message.ChannelGuid;
            messageRow.Content = message.Content;
            messageRow.ViewerGuid = message.ViewerGuid;
            messageRow.EditorGuid = message.EditorGuid;
            (messageRow as IMessage).CustomFields.Clear();
            foreach (var item in message.CustomFields)
            {
                (messageRow as IMessage).CustomFields.Add(item);
            }


            foreach (MasterDataSet.AttachmentsRow oldRow in header.Attachments)
            {
                oldRow.Delete();
            }
            foreach (IAttachment attachment in message.Attachments)
            {
                MasterDataSet.Attachments.AddAttachmentsRow(
                    Guid.NewGuid(),
                    header,
                    attachment.FileName,
                    attachment.Size,
                    attachment.RemoteUrl,
                    attachment.LocalPath,
                    attachment.Description
                    );
            }
            //update message row
            TableAdapterManager.Instance.UpdateAll(this.MasterDataSet);
            TableAdapterManager.Instance.MessagesTableAdapter.Update(messageRow);
            header.AcceptChanges();
            this.OnMessageChanged(messageRow);
        }

        public void DeleteMessage(Guid guid)
        {
            //Update message header
            MasterDataSet.MessageHeadersRow header = MasterDataSet.MessageHeaders.FindByGuid(guid);
            if (header == null || header.RowState == DataRowState.Deleted)
                return;

            header.Delete();

            this.OnMessageChanged(header);
        }

        public int GetUnreadMessageCount(Guid folderGuid, bool recursive)
        {
            MasterDataSet.FoldersRow folderRow = this.MasterDataSet.Folders.FindByGuid(folderGuid);

            if (folderRow == null || folderRow.RowState == DataRowState.Deleted)
                return 0;

            ICollection<IMessage> msgRows = folderRow.MessageHeaders;

            int count = 0;

            foreach (IMessage msg in msgRows)
            {
                if (!msg.Read)
                {
                    count++;
                }
            }

            if (recursive)
            {
                foreach (IFolder subfolder in folderRow.SubFolders)
                {
                    count += GetUnreadMessageCount(subfolder.Guid, true);
                }
            }

            return count;

        }

        public void MarkAllMessages(Guid folderGuid, bool read)
        {
            var folderRow = MasterDataSet.Folders.FindByGuid(folderGuid);
            if (folderRow == null || folderRow.RowState == DataRowState.Deleted)
                return;

            foreach (var header in folderRow.MessageHeaders)
            {
                header.Read = read;
            }

            foreach (var subFolder in folderRow.SubFolders)
            {
                MarkAllMessages(subFolder.Guid, read);
            }
        }

        #endregion

  //      public event RegisterContextUIEventHandler RegisterContextUI;

        #region IClientCore 成员



        public void StoreMessageHeader(IMessage message)
        {
            MasterDataSet.MessageHeadersRow header = this.MasterDataSet.MessageHeaders.FindByGuid(message.Guid);
            if (header == null || header.RowState == DataRowState.Deleted)
                return;

            DateTime updateTime = (message.UpdateTime > miniTime) ? message.UpdateTime : miniTime;
            DateTime publishTime = (message.PublishTime > miniTime) ? message.PublishTime : miniTime;

            header.Read = message.Read;
            header.Title = message.Title;
            header.PublishTime = publishTime;

            header.UpdateTime = updateTime;
            header.Author = message.Author;
            header.Category = message.Category;
            header.ReplyGuid = message.ReplyGuid;
            header.Receiver = message.Receiver;
            header.HashCode = message.HashCode;
        }

        #endregion

        #region IClientCore 成员


        public IFolder FindFolderByName(string name, Guid parentGuid)
        {
            var parentRow = this.MasterDataSet.Folders.FindByGuid(parentGuid);
            if (parentGuid == null || parentRow.RowState == DataRowState.Deleted)
                return null;

           
            foreach (var subFolder in parentRow.SubFolders)
            {
                if (subFolder.Name == name)
                    return subFolder;
            }
            return null;
        }


        public IMessage[] Search(string title, string author, bool unreadOnly)
        {
            StringBuilder pattern = new StringBuilder();

            bool firstPattern = true;

            if (title != null)
            {
                if (!firstPattern)
                    pattern.Append(" AND ");

                pattern.Append(string.Format("[Title] LIKE '%{0}%'", title));

                firstPattern = false;
            }

            if (author != null)
            {
                if (!firstPattern)
                    pattern.Append(" AND ");

                pattern.Append(string.Format("[Author] LIKE '%{0}%'", author));

                firstPattern = false;
            }

            if (unreadOnly)
            {
                if (!firstPattern)
                    pattern.Append(" AND ");

                pattern.Append(string.Format("[Read] = 'FALSE'"));

                firstPattern = false;
            }


            DataRow[] dataRows = this.MasterDataSet.MessageHeaders.Select(pattern.ToString(),"", DataViewRowState.CurrentRows);

            IMessage[] messages = new IMessage[dataRows.Length];

            for (int i = 0; i < dataRows.Length; ++i)
                messages[i] = (IMessage)dataRows[i];

            return messages;
        }

        #endregion

        public string GetAttachmentLocalPath(IMessage ownerMessage, IAttachment attachment)
        {

            if (attachment.LocalPath.Trim().Length == 0)
            {
                Stack<string> pathNames = new Stack<string>();

                IFolder cursor = ownerMessage.Folder;
                while (!cursor.IsRoot)
                {
                    pathNames.Push(cursor.Name);
                    
                    cursor = cursor.Parent;
                }

                string attachmentPath = (string)(AppDomain.CurrentDomain.GetData("DataDirectory"));

                while (pathNames.Count > 0)
                {
                    attachmentPath = Path.Combine(attachmentPath, pathNames.Pop().Replace(':', ' '));
                }

                attachmentPath = Path.Combine(attachmentPath, ownerMessage.Title.Replace(':', ' '));


                int i = 1;
                string tempPath = Path.Combine(attachmentPath, attachment.FileName.Replace(':', ' '));

                while (File.Exists(tempPath))
                {
                    tempPath = Path.Combine(
                        attachmentPath,
                        Path.GetFileNameWithoutExtension(attachment.FileName.Replace(':', ' ')) + 
                        string.Format(" ({0})", i) +
                        Path.GetExtension(attachment.FileName.Replace(':', ' '))
                        );
                }

                attachmentPath = tempPath;


                if (attachmentPath.IndexOfAny(Path.GetInvalidPathChars()) != -1)
                {
                    attachmentPath = (string)(AppDomain.CurrentDomain.GetData("DataDirectory"));
                    attachmentPath = Path.Combine(attachmentPath, attachment.Guid.ToString());
                }

                return attachmentPath;
            }
            else
            {
                return attachment.LocalPath;
            }
        }
    }
}
